Frida Hook 操作手册
作者:FloatingGuy 转载请注明出处:https://floatingguy.github.io/
Frida 支持的系统包括:
- Android(root/非 root),
- iOS(越狱/非越狱),
- MacOS(关闭 SIP),
- Linux, Windows等
Frida wiki 的这张表Hook工具兼容性对比 充分说明了frida的强大。
Frida 开发语言:JavaScript+python/C/objc
支持的系统版本:Android(<7.1)
目前主要的问题:
- 只能 hook so 中导出的函数,像 JNINativeInterface 中 函数指针就无法 hook
- 不是很稳定
优点:
- 可以动态植入 hook 代码
- java/native/系统库 均可以 hook
安装 & 配置
二进制frida 库: 从 realeas 下载
python模块: 从 pypi 下载
Device
$ adb push frida-server /data/local/tmp/
$ adb shell
# su
# cd /data/local/tmp/
# chmod 777 frida-server
# ./frida-server &PC
adb forward tcp:27042 tcp:27042
adb forward tcp:27043 tcp:27043
python && js 开发Api
import frida
导入 python 模块
获取 USB 设备
|
|
返回 frida.core.Device 对象。Device(id="emulator-5554", name="Android Emulator 5554", type='tether')
还可以获取 -R(Remote设备), -D (根据设备 ID 获取)。。
获取进程信息(Andorid)
命令
API
枚举某进程的模块及模块中导出的函数
- session.enumerate_modules()
- module.enumerate_exports()12345678910枚举某个进程加载的所有模块以及模块中的导出函数,dex 中没有导出函数session = dev.attach("com.tencent.mm")modules = session.enumerate_modules()for module in modules:print moduleexport_funcs = module.enumerate_exports()print "\tfunc_name\tRVA"for export_func in export_funcs:print "\t%s\t%s"%(export_func.name,hex(export_func.relative_address))
JavaScript API [补充]
这里只介绍我用过的 API,详情请看官方文档
Java
Hook Java 虚拟机,比如 Dalvik 或者 ART
Java.perform(fn)
只要frida 附加到目标进程的 VM 就会执行 fn 函数 (fn 是 js 函数)
Java.perform(function() {…})
栗子Java.enumerateLoadedClasses(callbacks)
枚举已经加载的 Classes 栗子1234Java.enumerateLoadedClasses({"onMatch": function (className) {...},"onComplete":function(){... } // 枚举完成})overload('xxx')
当要 hook 的函数有多个重载函数时,就需要通过 overload(目标函数参数类型
)指定函数的参数列表 栗子Java.use("package.xxx.className")
动态获取 className 对象 栗子, 可能使用出现的BugJava.choose("className", callbacks)
枚举所有存活的 className 对象,并调用 callbacks 函数 栗子1234Java.choose("className", {"onMatch": function(instance) {...},"onComplete":function(){... } // 枚举完成}
Interceptor
hook so/dylib native 函数
Interceptor.attach(target, callbacks)
附加到目标函数,当target 执行到时自动调用 callbacks 中的函数 栗子1234Interceptor.attach(target, {"onEnter": function (args) {...},"onLeave": function(retval){...}})
Memory
内存操作函数,内存地址的读写。
Memory.readCString(addr,
栗子Memory.readUtf8String()
栗子Memory.writeInt(addr, value)
Memory.writePointer(address, ptr)
Memory.protect(address, size, protection)
Memory.protect(ptr("0x1234"), 4096, 'rw-');
向so 库中指定偏移地址写入
Module
Module.enumerateImports(name, callbacks)
目标进程某个模块的导入表,包含导入的类型,函数地址/变量地址,模块名,函数名/变量名 栗子1234Module.enumerateImports(moduleName, {"onMatch":function(imp) {...},"onComplete":function() {...} // 枚举完成});Module.enumerateExports(name, callbacks)
目标进程某个模块的导出表, 包含导出的类型,函数地址/变量地址,模块名,函数名/变量名 栗子1234Module.enumerateExports(moduleName, {"onMatch":function(imp) {...},"onComplete":function() {...} // 枚举完成});
NativeFunction(address, returnType, argTypes[, abi])
创建一个native 函数对象。 目的值为了调用 原始的native函数。 栗子
NativeCallback(func, returnType, argTypes[, abi])
使用js 函数func 实现一个函数 栗子
RPC
rpc.exports
我的理解是这个功能是向 目标进程添加函数,方便 python 客户端直接调用;简单点说就是在目标进程写入自己的后门。1234567891011121314151617181920import codecsimport fridadef on_message(message, data):if message['type'] == 'send':print(message['payload'])elif message['type'] == 'error':print(message['stack'])rdev = frida.get_remote_device()session = rdev.attach('hello') #/data/local/tmp/hellowith codecs.open('./agent.js', 'r', 'utf-8') as f:source = f.read()script = session.create_script(source)script.on('message', on_message)script.load()print(script.exports.disassemble(0x4000100))print(script.exports.disassemble(0x4000100))session.detach()
|
|
我测试的时候 都失败了, 还是去看看 frida-presentations
PPT
Global
setImmediate(fn)
远程附加到进程时防止超时,可以先执行脚本(等待进程启动)1234setImmediate({console.log("[*] Starting script");Java.perform(function () {...}})
测试发现 这个方法好像不起作用。。
hexdump(traget [, options])
dump 指定地址的内存, target可以是 NativePointer 或者ArrayBuffer 数组
options 包括(都是十进制):
offset: int,
length: int,
header: boolean,
ansi: boolean
- 空指针
NULL: short-hand for ptr("0")
输出信息
send(msg)
发送msg给 python 代码script.on('message', on_message)
注册的 on_message 函数。 栗子console.log('Done:' + JSON.stringify(this.cnt));
向终端输出信息,并发送给script.on注册的回掉函数 栗子
完整的 Demo
这里推荐 Zhiwei Li 学习 Frida 的多篇 blog.
hook Native
Interceptor.attach
Memory.readCString
总结:
frida hook native 函数(地址)非常简单, 只需要知道 地址就可以通过 NativeCallback 或者 Interceptor.attach 随意hook。
基础案例
|
|
Hook 动态库中任意native 地址
此案例 可以用来hook so 中任意地址,哪怕不知道函数名 通过偏移找到地址就可以hook.(来源)
替换导入表函数
使用js函数替换pthread_create
函数实现
- 获取 pthread_create 函数指针
- 使用 1获取的指针 创建另一个native 函数
- 定义 NativeCallback 回调函数,重载这个方法
- 使用 Interceptor 的 replace 模式 去注入并替换原始的 pthread_create 函数
|
|
hook Java
hook java 同 hook native的代码(1-6步)完全一样,只是 javascript 部分不同,下面只列出 js 部分代码。
Java.use
send
其实完全可以只使用 js 代码来完成整个 hook工作。下一节介绍 通过 frida-cli 命令就可以在 pc 终端上就可以完成hook 流程。
iOS 上使用Frida
在iOS上使用 Frida-cli 工具必须要在越狱的手机上安装fridaServer,如果没有越狱的化就只能使用FridaGadget 来hook 指定的APP 操作不是很方便。推荐在越狱的机器上使用。
从cydia 安装的frida是V10.0.6,但是Mac 上之前使用的是 frida 9.2.7。所以要对 Mac 上的frida 进行升级,升级过程出现了一个pip 的bug
参考文章:在iOS应用程序中使用Frida绕过越狱检测 介绍了如下的功能:
- 在iOS上设置frida
- frida hook DVIA 反越狱检测的案例
案例 (待补充)
目前在iOS 上基于frida 流行的2个工具:needle 和 AppMon
Needle是一个开源的模块化框架,主要简化iOS应用程序安全评估过程,并作为一个中心点。鉴于其模块化方法,Needle很容易扩展新模块,可以以Python脚本的形式加入。
地址:https://github.com/mwrlabs/needle
AppMon是监测和修改本地macOS、iOS、Android系统API的自动化框架,并能通过web接口显示和操作
地址:https://github.com/dpnishant/appmon
使用Frida-cli Hook [推荐]
上面是通过编写 python && js 混合脚本进行 hook, 下面介绍 frida 命令(js 函数)通过 CLI hook。 这种方法更 cool, 而且使用的命令就是上面的Api。
这里分2种情况:
- 先启动一个应用,然后再让 Frida 进行附加
- 使用
-f <包名>
参数,让 Frida 自动生成进程,这种方法hook 的时机较早,可以用来 hook JNI_OnLoad 函数。
Frida附加进程
在设备上开启进程
执行命令load 脚本(提前编写好 js 脚本)
frida -U -l xxx.js com.tencent.mm
-l 注入脚本
如果进程不存在会显示BUG: Failed to attach: unable to find process with name ‘com.tencent.mm’
hook android.view.View class
Java.perform
Java.choose
console
Frida启动应用
以微信为例。
目标应用已经启动, 还可以添加 -l
参数在应用启动之前注入 hook 的代码。
测试文档中的几个命令
Hook android.app.Activity 的 onCreate(android.os.Bundle)
- overload(params) 当要 hook 的函数有多个重载函数时,使用 overload 指定函数参数
overload1234567Java.perform(function () {var Activity = Java.use("android.app.Activity");Activity.onCreate.overload('android.os.Bundle').implementation = function () {console.log("onCreate() got called! Let's call the original implementation");this.onCreate(arguments[0]);};});
重写 android.app.Activity::onCreate(android.os.Bundle)函数, overload 指定具体的 onCreate 版本。
Java app Classes
(警告:此处会输出很多内容,后面我会解释代码的意思。):
Java.enumerateLoadedClasses
Native Modules info
通过 js 构造原型对象类型发送给 on_message(), 方便on_message 同时处理有多个 hook 消息的情况。
- 查看 libbinder.so模块的导入表123456789101112131415161718Module.enumerateImports('libbinder.so', {"onMatch":function(imp) {var result = imp.type+" : "+ String(imp.address) +" "+ imp.module +" : "+ imp.namevar foo = {fun: 'import',data: result}send(foo)},"onComplete":function() {var foo = {fun: 'import',data: '#!over'}send(foo)}});
- 查看 libbinder.so 模块的导出表123456789101112131415161718Module.enumerateExports('libbinder.so', {"onMatch":function(exp) {var result = exp.type+" : "+ String(exp.address) +" "+" : "+ exp.namevar foo = {fun: 'export',data: result}send(foo)},"onComplete":function() {var foo = {fun: 'export',data: '#!over'}send(foo)}});
反调试
native层
监控pthread_create函数
|
|
监控 inotify
监控ptrace
java层
java 反反调试
反调试 最终一般都会选择退出进程,java 层通过System.exit()
函数退出进程。
ios反调试
练习
hook dlopen函数
|
|
打印 java 调用栈
通过 hook 目标函数,让其抛出 NullPointerException, 还要再开启一个终端,监控日志输出。
这里通过反射创建了 java.lang.NullPointerException
对象。
Terminal 2:adb logcat | grep --color=auto $pid
Frida 绕过 Android SSL Pinning
Android SSL Pinning 的目的是校验Https 通信中服务端的证书,防止攻击者进行中间人攻击。为了防止攻击者将自己的恶意证书加入到系统证书中,Android 提供了 SSL Pinning 技术可以在app中绑定服务端的证书,这样其他的证书就无法伪造服务端证书。
github 中有一个自实现Android SSL Pinning 的例子使用了Retrofit(类型安全的Http 客户端),我们分析下他的实现。
- 原理:
- 获取服务端的证书
openssl s_client -showcerts -connect api.github.com:443 </dev/null 2>/dev/null|openssl x509 -outform PEM >mycertfile.pem
可以将api.github.com
替换成希望校验的证书的服务器, 最后会生成 mycertfile.pem 证书文件 - 转换证书文件到 .bks 文件
将 pem/cert 格式的证书文件,转换成keystore文件(后缀.bks)。因为pem/cert 没有对应的api 来加载,所以要转换。
keytool -importcert -v -trustcacerts -file "mycertfile.pem" -alias ca -keystore "keystore.bks" -provider org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath "bcprov-ext-jdk15on-1.46.jar" -storetype BKS -storepass testing
转换成 keystore 文件时,如果添加了密钥,那么java.security.KeyStore
类在load 这个keystore 文件时 需要这个密钥。 - 代码中pinCertificates
- 加载Keystore 文件到
java.security.KeyStore
类 - 使用keystore 对象初始化
CustomTrustManager
类 - 使用keystore 对象初始化
KeyManagerFactory
类对象 (我猜这个对象里还包含了系统证书的信息) - 使用 KMF 和 用户定义的 CustomTrustManager 初始化 SSLContext
- 从 SSLContext 获取 SSLSocketFactory 对象
总结一下 keystore 的引用链:keystore文件->Keystore 对象 -> CustomTrustManager 对象 -> SSLContext 对象 -> SSLSocketFactory 对象
- 加载Keystore 文件到
|
|
SSL Pinning 的原理 已经清楚,下面介绍如何 绕过pinning 的检测。
注意观察上述代码 [1]
处第二个参数 就是用来 校验的TrustManagers,如果我们通过hook 替换了sslContext.ini
函数的第二个参数,那么SSL Pinning 就失效了。
- Frida脚本的工作:
- 从设备加载我们的 CA证书;
- 创建包含我们信任的CA证书的KeyStore;
- 创建一个TrustManager,使它信任我们的KeyStore中的CA证书。
|
|
mac 上尝试
在 MacOS 使用 Firda 需要关闭 SIP
没有关闭的化 无法 attach 目标进程。
Bug
Error: Not allowed outside Java.perform() callback
通过测试发现 Java.use、Java.cast 这2个方法不能放在 Java.perform 中使用,否则就会有这个 bug。
扩展
介绍 javascript 之前没接触过这门语言,看 Frida 之前学习了一下语法。
在知乎上搜索了一些资料,最后选择了2个学习的网站:
JavaScript语法
1.对象
- 原型对象
一个对象就是一个属性集合,并拥有一个独立的prototype(原型)对象。这个prototype可以是一个对象或者null。
|
|
我们拥有一个这样的结构,两个明显的自身属性和一个隐含的proto属性,这个属性是对foo原型对象的引用
2.变量
有如下三类基础数据类型:
- 数值类型:比如 123,120.50 等。
- 字符串类型:比如“This text string”。
布尔类型:比如 true or false。
另外两个常用类型:null 和 undefined,这两个类型均仅限定一个单一的值。
还支持上面介绍的 对象类型.
注意:JAVA语言并区分整数类型与浮点类型。JavaScript 中的数值均使用浮点值来表示。同时,按照 IEEE754 标准,JavaScript 用64位浮点格式来表示数。
在 JavaScript 编程过程中,必须先声明一个变量,这个变量才能被使用。
此外,变量是通过 “var” 来声明的,例子如下:
JavaScript 是一种无类型语言。这就是说, JavaScript 变量可以存储任何类型的值。与其他语言不同的是,我们不需要在变量声明阶段告诉变量其要存储的数据类型是什么。
JavaScript变量作用域
- 全局变量:全局变量具有全部整体范围的作用域,这意味着它可以在 JavaScript 代码任何地方定义。
- 局部变量:局部变量仅在定义它的函数体内可以访问到。函数参数对于函数来说就是局部变量。
|
|
JavaScript 保留的关键字
abstract | else | instanceof | switch |
boolean | enum | int | synchronized |
break | export | interface | this |
byte | extends | long | throw |
case | FALSE | native | throws |
catch | final | new | transient |
char | finally | null | TRUE |
class | float | package | try |
const | for | private | typeof |
continue | function | protected | var |
debugger | goto | public | void |
default | if | return | volatile |
delete | implements | short | while |
do | import | static | with |
double | in | super |
3.运算符
<< 称为按位左移运算符。它把第一个运算数的所有二进制位向左移动第二个运算数指定的位数,而新的二进制位补0。将一个数向左移动一个二进制位相当于将该数乘以2,向左移动两个二进制位相当于将该数乘以4,以此类推。 A << 1 = 4.
‘>>’ 称为按位右移运算符。它把第一个运算数的所有二进制位向右移动第二个运算数指定的位数。为了保持运算结果的符号不变,左边二进制位补0或1取决于原参数的符号位。如果第一个运算数是正的,运算结果最高位补0;如果第一个运算数是负的,运算结果最高位补1。将一个数向右移动一位相当于将该数乘以2,向右移动两位相当于将该数乘以4,以此类推。 A >> 1 = 1.
‘>>>’ 称为0补最高位无符号右移运算符。这个运算符与>>运算符相像,除了位移后左边总是补0. A >>> = 1.
4.函数
|
|
知识的搬运工今天都当到这里,剩下的就是 Coding
Frida 公开技术
这里给出翻译稿的链接,原文请自行查找
利用FRIDA攻击Android应用程序(一)
利用FRIDA攻击Android应用程序(二)
利用FRIDA攻击Android应用程序(三)
利用FRIDA攻击Android应用程序(四)
Example tool built for an Android CTF
非 Root 条件下,如何在 Android 上使用 Frida 框架
instrumenting-android-applications-with-frida
frida-presentations
基于 Frida 框架的 Objective-C 插桩方法
使用Frida绕过Android SSL Re-Pinning
利用Frida 给ios 内核patch , 绕过KPP
基于Frida 开发的Mac上动态调试工具 CryptoShark
(frida-gadget)在Android 非root环境上使用Frida